﻿using System;
using System.Data;
using HashFoo.SqlServer.Spatial;
using NHibernate;
using NHibernate.SqlTypes;
using NHibernate.UserTypes;

namespace HashFoo.Nhib.Spatial
{
    /// <summary>
    /// NHibernate user type that maps a MsSql Geometry column to a generic IGeography property.
    /// </summary>
    public class HashFooGeographyType : IUserType
    {
        #region IUserType Members
        /// <summary>
        /// Compare two instances of the class mapped by this type for persistent "equality"
        ///             ie. equality of persistent state
        /// </summary>
        /// <param name="x"/><param name="y"/>
        /// <returns/>
        bool IUserType.Equals(object x, object y)
        {
            if (x == null && y == null) return true;
            if (x == null || y == null) return false; // in theory, they can't be the same, if one is null and the other isn't

            var geography = x as Geography;
            var other = y as Geography;
            
            // if we have two geometries, use the Geography equality, else use defualt object equality
            return (geography == null || other == null) ? x.Equals(y) : geography.Equals(other);
        }

        /// <summary>
        /// Get a hashcode for the instance, consistent with persistence "equality"
        /// </summary>
        public int GetHashCode(object x)
        {
            return x.GetHashCode();
        }

        /// <summary>
        /// Retrieve an instance of the mapped class from a JDBC resultset.
        ///             Implementors should handle possibility of null values.
        /// </summary>
        /// <param name="rs">a IDataReader</param><param name="names">column names</param><param name="owner">the containing entity</param>
        /// <returns/>
        /// <exception cref="T:NHibernate.HibernateException">HibernateException</exception>
        public object NullSafeGet(IDataReader rs, string[] names, object owner)
        {
            var dbVal = NHibernateUtil.BinaryBlob.NullSafeGet(rs, names);
            if (dbVal == null) return null;

            var blob = (byte[])dbVal;
            return Geography.FromSqlServer(blob);
        }

        /// <summary>
        /// Write an instance of the mapped class to a prepared statement.
        ///             Implementors should handle possibility of null values.
        ///             A multi-column type should be written to parameters starting from index.
        /// </summary>
        /// <param name="cmd">a IDbCommand</param><param name="value">the object to write</param><param name="index">command parameter index</param><exception cref="T:NHibernate.HibernateException">HibernateException</exception>
        public void NullSafeSet(IDbCommand cmd, object value, int index)
        {
            if (value == null)
            {
                NHibernateUtil.BinaryBlob.NullSafeSet(cmd, null, index);
                return;
            }

            var sqlGeography = ((Geography)value).InternalGeography;
            NHibernateUtil.BinaryBlob.NullSafeSet(cmd, sqlGeography.Serialize(), index);
        }

        /// <summary>
        /// Return a deep copy of the persistent state, stopping at entities and at collections.
        /// </summary>
        /// <param name="value">generally a collection element or entity field</param>
        /// <returns>
        /// a copy
        /// </returns>
        public object DeepCopy(object value)
        {
            var geom = value as Geography;
            return geom == null ? value : geom.Clone();
        }

        /// <summary>
        /// During merge, replace the existing (<paramref name="target"/>) value in the entity
        ///             we are merging to with a new (<paramref name="original"/>) value from the detached
        ///             entity we are merging. For immutable objects, or null values, it is safe to simply
        ///             return the first parameter. For mutable objects, it is safe to return a copy of the
        ///             first parameter. For objects with component values, it might make sense to
        ///             recursively replace component values.
        /// </summary>
        /// <param name="original">the value from the detached entity being merged</param><param name="target">the value in the managed entity</param><param name="owner">the managed entity</param>
        /// <returns>
        /// the value to be merged
        /// </returns>
        public object Replace(object original, object target, object owner)
        {
            return original;
        }

        /// <summary>
        /// Reconstruct an object from the cacheable representation. At the very least this
        ///             method should perform a deep copy if the type is mutable. (optional operation)
        /// </summary>
        /// <param name="cached">the object to be cached</param><param name="owner">the owner of the cached object</param>
        /// <returns>
        /// a reconstructed object from the cachable representation
        /// </returns>
        public object Assemble(object cached, object owner)
        {
            return cached;
        }

        /// <summary>
        /// Transform the object into its cacheable representation. At the very least this
        ///             method should perform a deep copy if the type is mutable. That may not be enough
        ///             for some implementations, however; for example, associations must be cached as
        ///             identifier values. (optional operation)
        /// </summary>
        /// <param name="value">the object to be cached</param>
        /// <returns>
        /// a cacheable representation of the object
        /// </returns>
        public object Disassemble(object value)
        {
            return value;
        }

        /// <summary>
        /// The SQL types for the columns mapped by this type. 
        /// </summary>
        public SqlType[] SqlTypes
        {
            get { return new SqlType[] { SqlTypeFactory.GetBinaryBlob(2147483647) }; }
        }

        /// <summary>
        /// The type returned by <c>NullSafeGet()</c>
        /// </summary>
        public Type ReturnedType
        {
            get { return typeof(Geography); }
        }

        /// <summary>
        /// Are objects of this type mutable?
        /// </summary>
        public bool IsMutable
        {
            get { return true; }
        }
        #endregion
    }
}